package com.ziyan.processor;

import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Names;
import com.ziyan.annotation.EnumDesc;
import com.ziyan.cache.BaseIEnumCache;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.ziyan.annotation.EnumDesc")
public class EnumDescProcessor extends AbstractProcessor {


    /**
     * 用于将创建的类写入到文件
     */
    private Filer filer;

    private Context context;

    private JavacElements elementUtils;

    private TreeMaker treeMaker;

    private Names names;

    private Messager messager;

    private Types typeUtils;

    @Override
    public synchronized void init(ProcessingEnvironment environment) {
        super.init(environment);
        filer = environment.getFiler();

        context = ((JavacProcessingEnvironment) processingEnv).getContext();
        elementUtils = (JavacElements) processingEnv.getElementUtils();
        treeMaker = TreeMaker.instance(context);

        names = Names.instance(context);
        messager = environment.getMessager();
        typeUtils = environment.getTypeUtils();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        // 这个方法是注解处理器的核心，稍后单独分析这个方法如何实现
        // 获取所有被 @DemoAnnotation 注解的类
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(EnumDesc.class);

        // 创建一个方法，返回 Set<Class>
        for (Element element : elements) {
            if (element instanceof Symbol.VarSymbol) {
                createMethodWithElement((Symbol.VarSymbol) element);
            }
        }
        return false;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        // 这个方法返回当前处理器 能处理哪些注解，这里我们只返回
        return Collections.singleton(EnumDesc.class.getCanonicalName());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        // 这个方法返回当前处理器 支持的代码版本
        return SourceVersion.latestSupported();
    }

    /**
     * 创建一个方法，这个方法返回 elements 中的所有类信息。
     */
    private void createMethodWithElement(Symbol.VarSymbol varSymbol) {

        JCTree.JCClassDecl classDecl = (JCTree.JCClassDecl) elementUtils.getTree(varSymbol.owner);
        JCTree.JCVariableDecl variableDecl = (JCTree.JCVariableDecl) elementUtils.getTree(varSymbol);

        String filedName = variableDecl.getName().toString();
        String getterName = "get".concat(filedName.substring(0, 1).toUpperCase().concat(filedName.substring(1)));

        EnumDesc enumDesc = varSymbol.getAnnotation(EnumDesc.class);
        Type enumClass = null;
        try {
            enumDesc.enumClass();
        } catch (MirroredTypeException x) {
            enumClass = (Type) x.getTypeMirror();
        }
        final Type enumClassFinal = enumClass;

        Optional<JCTree> srcFieldOpt = classDecl.getMembers().stream().filter(o -> o instanceof JCTree.JCVariableDecl && ((JCTree.JCVariableDecl) o).name.toString().equals(enumDesc.field())).findFirst();
        if (!srcFieldOpt.isPresent()) {
            messager.printMessage(Diagnostic.Kind.ERROR, "类不存在成员变量：" + enumDesc.field(), varSymbol);
            return;
        }
        if (varSymbol.name.toString().equals(enumDesc.field())) {
            messager.printMessage(Diagnostic.Kind.ERROR, "注解“@EnumDesc”字段“field”不能为:" + enumDesc.field(), varSymbol);
            return;
        }
        if (!varSymbol.type.toString().equals("java.lang.String")) {
            messager.printMessage(Diagnostic.Kind.ERROR, "“@EnumDesc”修饰的成员变量必须是String类型", varSymbol);
            return;
        }

        classDecl.accept(new TreeTranslator() {

            @Override
            public void visitClassDef(JCTree.JCClassDecl jcClass) {
                super.visitClassDef(jcClass);
                // 方法处理
                jcClass.getMembers().stream().filter(o -> o instanceof JCTree.JCMethodDecl && ((JCTree.JCMethodDecl) o).name.toString().equals(getterName))
                    .map(o -> (JCTree.JCMethodDecl) o).findFirst().ifPresent(jcMethodDecl -> {
                    ListBuffer<JCTree.JCStatement> jcStatements = new ListBuffer<>();

                    //添加语句 " return this.xxx; "
                    jcStatements.append(
                        treeMaker.Return(
                            treeMaker.Apply(
                                List.nil(),
                                treeMaker.Select(
                                    treeMaker.Select(
                                        treeMaker.Ident(names.fromString("com.ziyan.cache")),
                                        elementUtils.getTypeElement(BaseIEnumCache.class.getCanonicalName())
                                    ),
                                    elementUtils.getName("getDesc")
                                ),
                                List.of(
                                    treeMaker.ClassLiteral(enumClassFinal),
                                    treeMaker.Select(
                                        treeMaker.Ident(names._this),
                                        ((JCTree.JCVariableDecl) srcFieldOpt.get()).getName()
                                    )
                                )
                            )
                        )
                    );

                    JCTree.JCBlock jcBlock = treeMaker.Block(
                        0 //访问标志
                        , jcStatements.toList() //所有的语句
                    );

                    jcMethodDecl.body = jcBlock;
                });
            }
        });
    }
}